home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1997 June: System Software / Dev.CD Jun 97 SSW.toast / What's New? / Sample Code / Text / NeoTextBox97 / NeoTextBox.c < prev    next >
Encoding:
C/C++ Source or Header  |  1997-04-23  |  11.8 KB  |  357 lines  |  [TEXT/CWIE]

  1. /********************************************************************************
  2.  
  3. NeoTextBox.c - a replacement for TextBox
  4.  
  5. Written by Bryan K. Ressler (Beaker)
  6.  
  7. updated for the wonderful universe of PowerPC by Pete Gontier (Gurgle), 3/31/97
  8.  
  9. ********************************************************************************/
  10.  
  11. #include "NeoTextBox.h"
  12.  
  13. #ifndef __QUICKDRAW__
  14. #    include <QuickDraw.h>
  15. #endif
  16.  
  17. #ifndef __FONTS__
  18. #    include <Fonts.h>
  19. #endif
  20.  
  21. #ifndef __SCRIPT__
  22. #    include <Script.h>
  23. #endif
  24.  
  25. #ifndef __TEXTUTILS__
  26. #    include <TextUtils.h>
  27. #endif
  28.  
  29. #ifndef __TEXTEDIT__
  30. #    include <TextEdit.h>
  31. #endif
  32.  
  33. #ifndef __FIXMATH__
  34. #    include <FixMath.h>
  35. #endif
  36.  
  37. /*******************************************************************************/
  38.  
  39. #define MAXOF(a,b)    (((a) > (b)) ? (a) : (b))
  40.  
  41. /********************************************************************************
  42. *
  43. * NTBLineHeight - figures line height
  44. *
  45. * Input:    theText        the entire text that was given to the NeoTextBox call
  46. *            textLen        the length in bytes of the text
  47. *            lhCode        the line height code that was passed to NeoTextBox
  48. *            startY        VAR - we return the starting vertical pen location here
  49. *
  50. * Output:    returns the line height to use
  51. *            
  52. ********************************************************************************/
  53.  
  54. static pascal UInt16 NTBLineHeight
  55.     (unsigned char *theText, unsigned long textLen,
  56.         Rect *wrapBox, short lhCode, short *startY)
  57. {
  58.     short            asc,desc;    /* Used in the OutlineMetrics calls */
  59.     FontInfo        fInfo;        /* The old-style font information record */
  60.     Point            frac;        /* The fraction for the TrueType calls */
  61.     unsigned short    lineHeight;    /* The return value */
  62.  
  63.     GetFontInfo(&fInfo);
  64.     if (lhCode < 0) {
  65.  
  66.         /*
  67.             If the user has specified variable-height lines, we need to try
  68.             to determine the tallest ascent in the given text.  We can only
  69.             really do this if the font is a TrueType font.  Otherwise, we
  70.             punt and use old-fashioned GetFontInfo numbers.
  71.         */
  72.  
  73.         frac.h = frac.v = 1;
  74.         if (IsOutline(frac, frac)) {
  75.  
  76.             /*
  77.                 At this point we know the current font is a TrueType font, so
  78.                 we do an OutlineMetrics call with our full text.  It will put
  79.                 the tallest character ascent into asc, and the deepest descent
  80.                 into desc.  Then we choose between whichever's most between
  81.                 the old-style ascent/descent and the numbers we get from
  82.                 the OutlineMetrics call.
  83.             */
  84.  
  85.             OutlineMetrics((short)textLen, theText, frac, frac, &asc, &desc,
  86.                 nil, nil, nil);
  87.             lineHeight = MAXOF(fInfo.ascent, asc) + MAXOF(fInfo. descent,-desc) +
  88.                 fInfo.leading;
  89.             *startY = wrapBox->top + MAXOF(fInfo.ascent, asc);
  90.             *startY += fInfo.leading;
  91.  
  92.         } else {
  93.         
  94.             /*
  95.                 At this point we know the current font isn't TrueType, so we
  96.                 just use the old way of calculating line height.
  97.             */
  98.             
  99.             lineHeight = fInfo.ascent + fInfo.descent + fInfo.leading;
  100.             *startY = wrapBox->top + fInfo.ascent + fInfo.leading;
  101.         }
  102.  
  103.     } else if (lhCode == 0) {
  104.  
  105.         /*
  106.             If the user has specified "default" line height, he just wants us
  107.             to get the line height from the FontInfo record.
  108.         */
  109.         
  110.         lineHeight = fInfo.ascent + fInfo.descent + fInfo.leading;
  111.         *startY = wrapBox->top + fInfo.ascent + fInfo.leading;
  112.         
  113.     } else {
  114.         
  115.         /* If the user has provided a specific line height, we just trust
  116.             them.  We can't really generate too good of a starting vertical
  117.             coordinate, but we munge one together anyway.
  118.         */
  119.  
  120.         lineHeight = lhCode;
  121.         *startY = wrapBox->top + lhCode + fInfo.leading;
  122.  
  123.     }
  124.  
  125.     return(lineHeight);
  126. }
  127.  
  128. /********************************************************************************
  129. *
  130. * NTBDraw - draws a line with appropriate justification
  131. *
  132. * Input:    breakCode    the break code that was returned from StyledLineBreak
  133. *            lineStart    pointer to the beginning of the text for the current line
  134. *            lineBytes    the length in bytes of the the text for this line
  135. *            wrapBox        the box within which we're wrapping
  136. *            align        the text alignment as specified by the user
  137. *            curY        our current vertical pen coordinate
  138. *            boxWidth    the width of wrapBox (since NeoTextBox already calculated it)
  139. *
  140. * Output:    none (draws on the screen)
  141. *            
  142. ********************************************************************************/
  143.  
  144. static pascal void NTBDraw
  145.     (StyledLineBreakCode breakCode, Ptr lineStart, long lineBytes,
  146.         Rect *wrapBox, short align, short curY, short boxWidth)
  147. {
  148.     unsigned long    blackLen;    /* Length of non-white characters */
  149.     short            slop;        /* Number of pixels of slop for full just */
  150.  
  151.     /*
  152.         The first thing we do here is determine the length of the "black" part
  153.         of the current line.  This excludes spaces, carriage returns, and other
  154.         white stuff depending on the language.  How do we know what to elim-
  155.         inate?  We DON'T!  So we ask our friend the Script Manager to do it
  156.         for us.  VisibleLength returns the number of bytes that we should use
  157.         for pixel width calculations.
  158.     */
  159.     
  160.     blackLen = VisibleLength(lineStart, lineBytes);
  161.     
  162.     if (align == ntbJustFull) {
  163.     
  164.         /*
  165.             For full justification, we need to calculate the "slop" space on
  166.             the line that's not filled up by the text.  Then we move to the
  167.             margin and get ready to draw BUT WAIT!  If this is the last line of
  168.             a paragraph, we need to draw it with whatever the system justifi-
  169.             cation is.  So if the break code indicates we're at the end of the
  170.             text, or we can find a carriage return there ourselves, we just
  171.             change the text alignment to the current default system text align-
  172.             ment and fall through without drawing.  If it's just another line
  173.             within a paragraph, we use the handy Script Manager routine DrawJust
  174.             to draw it justified.  In languages like Arabic, full justification
  175.             is performed differently than just spacing out the words.  DrawJust
  176.             handles cases like these correctly.
  177.             
  178.             Note that when we go looking for the carriage return at the end of
  179.             the line, it's okay to simply index to the last byte of the string.
  180.             This is because the carriage return character, 0x0d, is in the
  181.             control-code range, which is defined to never be the low byte of a
  182.             two byte character.
  183.         */
  184.  
  185.         slop = boxWidth - TextWidth(lineStart, 0, blackLen);
  186.         MoveTo(wrapBox->left, curY);
  187.         if (breakCode == smBreakOverflow ||
  188.             *(lineStart + (lineBytes - 1)) == kReturnChar)
  189.             align = GetSysDirection ( );
  190.         else DrawJust(lineStart, blackLen, slop);
  191.     }
  192.  
  193.     /*
  194.         For the rest of the text alignments (left, center, and right), we just
  195.         move the pen to the right place and draw the text with DrawText.  Note
  196.         that the alignments that could have come into the NeoTextBox call have
  197.         been resoved down to one of the four constants teForceLeft, teJustRight
  198.         teJustCenter, or ntbJustFull.
  199.     */
  200.     
  201.     switch(align) {
  202.         case teForceLeft:
  203.         case teJustLeft:
  204.             MoveTo(wrapBox->left, curY);
  205.             break;
  206.         case teJustRight:
  207.             MoveTo(wrapBox->right - TextWidth(lineStart, 0, blackLen), curY);
  208.             break;
  209.         case teJustCenter:
  210.             MoveTo(wrapBox->left + (boxWidth - TextWidth(lineStart, 0, blackLen)) / 2,
  211.                 curY);
  212.             break;
  213.     }
  214.     if (align != ntbJustFull)
  215.         DrawText(lineStart, 0, lineBytes);
  216. }
  217.  
  218. /********************************************************************************
  219. *
  220. * NeoTextBox - word-wraps text inside a given box
  221. *
  222. * Input:    theText        the text we need to wrap
  223. *            textLen        the length in bytes of the text
  224. *            wrapBox        the box within which we're wrapping
  225. *            align        the text alignment
  226. *                            teForceLeft, teFlushLeft    left justified
  227. *                            teJustCenter, teCenter        center justified
  228. *                            teJustRight, teFlushRight    right justified
  229. *                            ntbJustFull                    full justified
  230. *                            teJustLeft, teFlushDefault    system justified
  231. *            lhCode        the line height code that was passed to NeoTextBox
  232. *                            < 0        variable - based on tallest character
  233. *                            0        default - based on GetFontInfo
  234. *                            > 0        fixed - use lhCode as the line height
  235. *            endY        VAR - if non-nil, the vertical coord of the last line
  236. *            lhUsed        VAR - if non-nil, the line height used to draw the text
  237. *
  238. * Output:    returns the number of line drawn total (even if they drew outside of
  239. *            the boundries of wrapBox)
  240. *            
  241. ********************************************************************************/
  242. short NeoTextBox (void *theText, unsigned long textLen, Rect *wrapBox,
  243.     short align, short lhCode, short *endY, short *lhUsed)
  244. {
  245.     RgnHandle            oldClip;        /* Saved clipping region */
  246.     StyledLineBreakCode    breakCode;        /* Returned code from StyledLineBreak */
  247.     Fixed                fixedMax;        /* boxWidth converted to fixed point */
  248.     Fixed                wrapWid;        /* Width to wrap to */
  249.     short                boxWidth;        /* Width of the wrapBox */
  250.     long                lineBytes;        /* Number of bytes in one line */
  251.     unsigned short        lineHeight;        /* Calculated line height */
  252.     short                curY;            /* Current vertical pen location */
  253.     unsigned short        lineCount;        /* Number of lines we've drawn */
  254.     long                textLeft;        /* Pointer to remaining bytes of text */
  255.     Ptr                    lineStart;        /* Pointer to beginning of a line */
  256.     Ptr                    textEnd;        /* Pointer to the end of input text */
  257.  
  258.     /*
  259.         First, we save the old clipping region and clip to wrapBox.  Then, figure
  260.         the width of wrapBox, and make a fixed point version of it.  Also, resolve
  261.         the text alignment teFlushDefault to be the default system text alignment.
  262.     */
  263.  
  264.     GetClip((oldClip = NewRgn()));
  265.     ClipRect(wrapBox);
  266.     boxWidth = wrapBox->right - wrapBox->left;
  267.     fixedMax = Long2Fix((long)boxWidth);
  268.     if (align == teFlushDefault)
  269.         align = GetSysDirection ( );
  270.     
  271.     /*
  272.         Now we call NTBLineHeight to calculate the appropriate line height.  It
  273.         also figures our starting vertical pen location in curY based on the
  274.         line height and other metric parameters.  Clear lineCount, set
  275.         lineStart to point to the beginning of the text, calculate textEnd for
  276.         comparison to know when we're done, and preset textLeft to be all the
  277.         text.
  278.     */
  279.  
  280.     lineHeight = NTBLineHeight(theText, textLen, wrapBox, lhCode, &curY);
  281.     lineCount = 0;
  282.     lineStart = theText;
  283.     textEnd = ((Ptr) theText) + textLen;
  284.     textLeft = textLen;
  285.     
  286.     /*
  287.         This is the main wrap-and-draw loop.  I bet you never thought wrapping
  288.         text could be so easy...
  289.     */
  290.  
  291.     do {
  292.         
  293.         /*
  294.             Every line, we have to preset lineBytes to something non-zero.
  295.             This tells StyledLineBreak that we're drawing the first format
  296.             run on the line (of course, for us, there's only ONE format run
  297.             total).  Also preset wrapWid.  StyledLineBreak will always modify
  298.             lineBytes (to tell you how many bytes are on this line), and will
  299.             modify wrapWid, so we have to reset them each line.
  300.         */
  301.         
  302.         lineBytes = 1;
  303.         wrapWid = fixedMax;
  304.  
  305.         breakCode = StyledLineBreak(lineStart, textLeft, 0, textLeft, 0,
  306.             &wrapWid, &lineBytes);
  307.         
  308.         /*
  309.             Now that the Script Manager has done all the really hard work for
  310.             us, we draw the line.  We already knew lineStart, StyledLineBreak
  311.             gave us lineBytes, which we pass to NTBDraw.  It'll handle the
  312.             different text alignments itself.
  313.         */
  314.         
  315.         NTBDraw(breakCode, lineStart, lineBytes, wrapBox, align, curY, boxWidth);
  316.         
  317.         /*
  318.             Now we advance our vertical position down by the height of one
  319.             line, advance lineStart by the number of bytes we just drew,
  320.             calculate a new textLeft, and increment our line count.
  321.         */
  322.         
  323.         curY += lineHeight;
  324.         lineStart += lineBytes;
  325.         textLeft -= lineBytes;
  326.         lineCount++;
  327.         
  328.     } while (lineStart < textEnd);
  329.     
  330.     /*
  331.         Well that was a job well done.  Let's return some useful values, too.
  332.         If the user gave pointers for endY and lhUsed, we stuff our ending
  333.         vertical coordinate and the line height we calculated into those,
  334.         respectively.  These allow the guy to put something after the wrapped
  335.         text (or at least know the right place to put it).
  336.     */
  337.  
  338.     if (endY)
  339.         *endY = curY - lineHeight;
  340.     if (lhUsed)
  341.         *lhUsed = lineHeight;
  342.     
  343.     /*
  344.         Finally, restore the clipping region, dispose of the region, and
  345.         return the TOTAL number of lines drawn (note that we didn't stop
  346.         drawing when curY advanced past wrapBox->bottom.  This way, the user
  347.         could tell that the text overflowed wrapBox.  If you want to know how
  348.         many lines fit, divide wrapBox by lhUsed.  This way, you get the best
  349.         of both worlds.
  350.     */
  351.  
  352.     SetClip(oldClip);
  353.     DisposeRgn(oldClip);
  354.  
  355.     return(lineCount);
  356. }
  357.